package app.server.project.main.service.service.rank;

import app.server.project.core.constants.Def;
import app.server.project.main.service.bo.event.CommonRankEvent;
import app.server.project.main.service.db.dao.cfg.RankCfgDao;
import app.server.project.main.service.db.dao.user.UserExtLock;
import app.server.project.main.service.db.dao.user.UserInfoDao;
import app.server.project.main.service.db.dao.user.UserRankDao;
import app.server.project.main.service.db.entity.user.UserRankEntity;
import app.server.project.main.service.dto.rank.CommonRankDto;
import app.server.project.main.service.dto.rank.CommonRankResp;
import app.server.project.main.service.service.LockService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

@Data
@Slf4j
public abstract class CommonRank {


    private final static int MAX = 50000;
    private final static int LAZY_RATE = 1000;
    private final static int DB_MAX = 300;
    private final static int TimeCache = 1;
    /**
     * 上榜人数
     */
    private int rankNum = 100;
    /**
     * 奖励名次
     */
    private int rewardNum = 1;
    private int rankId;
    private ConcurrentLinkedQueue<CommonRankEvent> queue = new ConcurrentLinkedQueue<CommonRankEvent>();
    private List<CommonRankDto> rankDtoList = new ArrayList<>();
    @Autowired
    private UserInfoDao userInfoDao;

    @Autowired
    private RankCfgDao rankCfgDao;
    @Autowired
    private UserRankDao userRankDao;

    @Autowired
    private UserExtLock userExtLock;
    @Autowired
    private LockService lockService;


    private Cache<Long, UserRankEntity> cache = Caffeine.newBuilder()
            .expireAfterAccess(TimeCache, TimeUnit.HOURS)
            .build();


    public long getNextSettlementTime() {
        return Def.Zero;
    }

    /**
     * 结算逻辑
     */
    public abstract void doSettlement();

    public void init(int rankId) {
        this.rankId = rankId;
        this.initRank();
    }

    private void initRank() {
        List<CommonRankDto> tempRank = new ArrayList<>();
        var list = this.userRankDao.getRankByRankId(this.rankId, this.rankNum);
        int pos = 1;
        for (int i = 0; i < list.size(); i++) {
            var data = this.userInfoDao.getUserInfoById(list.get(i).getUserId());
            var dto = CommonRankDto.of(list.get(i).getVal(), data.getUserId(), data.getNick(), data.getHeadUrl(), pos);
            dto.setPos(pos);
            pos++;
            tempRank.add(dto);
        }
        this.rankDtoList = tempRank;
    }


    public synchronized void beginSettlement() {
        try {
            this.cache.invalidateAll();
            this.doSettlement();
        } catch (Exception e) {
            log.error("排行榜id:{},结算错误:{}", this.rankId, e);
        }
        log.info("排行榜id:{}结算成功", this.rankId);
        this.initRank();
    }

    /**
     * 读取排行榜
     *
     * @return
     */
    public CommonRankResp getRank(long targetUserId) {
        var my = this.rankDtoList.stream().filter(v -> v.getUserId() == targetUserId).findFirst();
        if (my.isPresent()) {
            return CommonRankResp.of(this.rankDtoList, my.get().getPos(), my.get().getVal());
        } else {
            var cache = this.getUserRankData(targetUserId, this.rankId);
            return CommonRankResp.of(this.rankDtoList, Def.Zero, cache.getVal());
        }
    }

    /**
     * 拉取数据
     *
     * @param userId
     * @param rankId
     * @return
     */
    public UserRankEntity getUserRankData(long userId, int rankId) {
        var ret = this.cache.getIfPresent(userId);
        if (Objects.nonNull(ret)) return ret;
        var lock = this.lockService.getLock(userId);
        synchronized (lock) {
            ret = this.cache.getIfPresent(userId);
            if (Objects.nonNull(ret)) return ret;
            ret = this.userRankDao.getByIds(userId, rankId);
            this.cache.put(userId, ret);
            return ret;
        }
    }


    /**
     * 添加排行榜变更值
     *
     * @param event
     */
    @EventListener(CommonRankEvent.class)
    public void addRankVal(CommonRankEvent event) {
        if (event.getRankId() != this.rankId) {
            return;
        }
        this.queue.offer(event);
    }


    /**
     * 同步数据库
     *
     * @return
     */
    private List<CommonRankEvent> flushDb() {
        List<CommonRankEvent> rankEvents = new ArrayList<>();
        int max = DB_MAX;
        while (max > 0) {
            var event = this.queue.poll();
            if (event == null) break;
            var db = this.getUserRankData(event.getUserId(), this.rankId);

            var userExtEntity = this.userExtLock.getUserExtByUserExtId(event.getUserId());
            if (userExtEntity.getInnerType() == Def.True) {
                db.setVal(Def.Zero);
            } else {
                if (event.isAdd()) {
                    db.addVal(event.getVal());
                } else {
                    db.consumeVal(event.getVal());
                }
            }
            rankEvents.add(event);
            max--;
        }
        return rankEvents;
    }

    /**
     * 排序
     *
     * @param events
     */
    private void sort(List<CommonRankEvent> events) {
        if (events.isEmpty()) return;
        events.forEach(v -> {
            var userExtEntity = this.userExtLock.getUserExtByUserExtId(v.getUserId());
            //下榜
            if (userExtEntity.getInnerType() == Def.True) {
                //先移除
                this.rankDtoList.removeIf(p -> p.getUserId() == v.getUserId());
            } else {
                var userInfo = this.userInfoDao.getUserInfoById(v.getUserId());

                //榜单列表有这个用户数据
                var ranker = this.rankDtoList.stream().filter(r -> r.getUserId() == v.getUserId()).findFirst();
                if (ranker.isPresent()) {
                    if (v.isAdd()) {
                        ranker.get().addVal(v.getVal());
                    } else {
                        ranker.get().consumeVal(v.getVal());
                    }
                } else {
                    //再添加
                    var db = this.getUserRankData(v.getUserId(), this.rankId);
                    var rankDto = CommonRankDto.of(db.getVal(), userInfo.getUserId(), userInfo.getNick(), userInfo.getHeadUrl(), Def.Zero);
                    this.rankDtoList.add(rankDto);
                }
            }
        });
        this.rankDtoList.sort(Comparator.comparingLong(CommonRankDto::getVal).reversed());
        for (int i = 0; i < this.rankDtoList.size(); i++) {
            this.rankDtoList.get(i).setPos(i + 1);
        }

        if (this.rankDtoList.size() > this.rankNum) {
            this.rankDtoList = this.rankDtoList.subList(Def.Zero, this.rankNum);
        }

    }

    @Scheduled(fixedDelay = LAZY_RATE)
    public synchronized void consume() {
        var events = this.flushDb();
        this.sort(events);
    }
}
